<?php

require_once 'HTMLPurifier/AttrTypes.php';

/**
 * Defines common attribute collections that modules reference
 */

class HTMLPurifier_AttrCollections
{
	
	/**
	 * Associative array of attribute collections, indexed by name
	 */
	var $info = array();
	
	/**
	 * Performs all expansions on internal data for use by other inclusions
	 * It also collects all attribute collection extensions from
	 * modules
	 * @param $attr_types HTMLPurifier_AttrTypes instance
	 * @param $modules Hash array of HTMLPurifier_HTMLModule members
	 */
	function HTMLPurifier_AttrCollections($attr_types, $modules) {
		// load extensions from the modules
		foreach ($modules as $module) {
			foreach ($module->attr_collections as $coll_i => $coll) {
				if (!isset($this->info[$coll_i])) {
					$this->info[$coll_i] = array();
				}
				foreach ($coll as $attr_i => $attr) {
					if ($attr_i === 0 && isset($this->info[$coll_i][$attr_i])) {
						// merge in includes
						$this->info[$coll_i][$attr_i] = array_merge(
							$this->info[$coll_i][$attr_i], $attr);
						continue;
					}
					$this->info[$coll_i][$attr_i] = $attr;
				}
			}
		}
		// perform internal expansions and inclusions
		foreach ($this->info as $name => $attr) {
			// merge attribute collections that include others
			$this->performInclusions($this->info[$name]);
			// replace string identifiers with actual attribute objects
			$this->expandIdentifiers($this->info[$name], $attr_types);
		}
	}
	
	/**
	 * Takes a reference to an attribute associative array and performs
	 * all inclusions specified by the zero index.
	 * @param &$attr Reference to attribute array
	 */
	function performInclusions(&$attr) {
		if (!isset($attr[0])) return;
		$merge = $attr[0];
		$seen  = array(); // recursion guard
		// loop through all the inclusions
		for ($i = 0; isset($merge[$i]); $i++) {
			if (isset($seen[$merge[$i]])) continue;
			$seen[$merge[$i]] = true;
			// foreach attribute of the inclusion, copy it over
			if (!isset($this->info[$merge[$i]])) continue;
			foreach ($this->info[$merge[$i]] as $key => $value) {
				if (isset($attr[$key])) continue; // also catches more inclusions
				$attr[$key] = $value;
			}
			if (isset($this->info[$merge[$i]][0])) {
				// recursion
				$merge = array_merge($merge, $this->info[$merge[$i]][0]);
			}
		}
		unset($attr[0]);
	}
	
	/**
	 * Expands all string identifiers in an attribute array by replacing
	 * them with the appropriate values inside HTMLPurifier_AttrTypes
	 * @param &$attr Reference to attribute array
	 * @param $attr_types HTMLPurifier_AttrTypes instance
	 */
	function expandIdentifiers(&$attr, $attr_types) {
		
		// because foreach will process new elements we add, make sure we
		// skip duplicates
		$processed = array();
		
		foreach ($attr as $def_i => $def) {
			// skip inclusions
			if ($def_i === 0) continue;
			
			if (isset($processed[$def_i])) continue;
			
			// determine whether or not attribute is required
			if ($required = (strpos($def_i, '*') !== false)) {
				// rename the definition
				unset($attr[$def_i]);
				$def_i = trim($def_i, '*');
				$attr[$def_i] = $def;
			}
			
			$processed[$def_i] = true;
			
			// if we've already got a literal object, move on
			if (is_object($def)) {
				// preserve previous required
				$attr[$def_i]->required = ($required || $attr[$def_i]->required);
				continue;
			}
			
			if ($def === false) {
				unset($attr[$def_i]);
				continue;
			}
			
			if ($t = $attr_types->get($def)) {
				$attr[$def_i] = $t;
				$attr[$def_i]->required = $required;
			} else {
				unset($attr[$def_i]);
			}
		}
		
	}
	
}

